Allow tables in configuration
authorAlex Crichton <alex@alexcrichton.com>
Mon, 14 Jul 2014 17:15:53 +0000 (10:15 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Mon, 14 Jul 2014 18:28:24 +0000 (11:28 -0700)
src/cargo/ops/cargo_compile.rs
src/cargo/util/config.rs

index 18b0c0587c162857453fee772f31c9949e97763d..44fba5c8038aaf2fa01dd13f6d7945b198978c42 100644 (file)
@@ -113,7 +113,9 @@ fn source_ids_from_config() -> CargoResult<Vec<SourceId>> {
 
     let paths: Vec<Path> = match *config_paths.get_value() {
         config::String(_) => return Err(internal("The path was configured as \
-                                                   a String instead of a List")),
+                                                  a String instead of a List")),
+        config::Table(_) => return Err(internal("The path was configured as \
+                                                 a Table instead of a List")),
         config::List(ref list) => {
             list.iter().map(|path| Path::new(path.as_slice())).collect()
         }
index 1745ae1ce78041a2105647c43521321c37a4c592..7525e7787910ed370cd07f941ae29acd245cd15b 100644 (file)
@@ -1,4 +1,4 @@
-use std::{io,fmt,os, result};
+use std::{io, fmt, os, result, mem};
 use std::collections::HashMap;
 use serialize::{Encodable,Encoder};
 use toml;
@@ -69,30 +69,27 @@ pub enum Location {
 #[deriving(Eq,PartialEq,Clone,Decodable)]
 pub enum ConfigValueValue {
     String(String),
-    List(Vec<String>)
+    List(Vec<String>),
+    Table(HashMap<String, ConfigValue>),
 }
 
 impl fmt::Show for ConfigValueValue {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        match self {
-            &String(ref string) => write!(f, "{}", string),
-            &List(ref list) => write!(f, "{}", list)
+        match *self {
+            String(ref string) => write!(f, "{}", string),
+            List(ref list) => write!(f, "{}", list),
+            Table(ref table) => write!(f, "{}", table),
         }
     }
 }
 
 impl<E, S: Encoder<E>> Encodable<S, E> for ConfigValueValue {
     fn encode(&self, s: &mut S) -> Result<(), E> {
-        match self {
-            &String(ref string) => {
-                raw_try!(string.encode(s));
-            },
-            &List(ref list) => {
-                raw_try!(list.encode(s));
-            }
+        match *self {
+            String(ref string) => string.encode(s),
+            List(ref list) => list.encode(s),
+            Table(ref table) => table.encode(s),
         }
-
-        Ok(())
     }
 }
 
@@ -110,6 +107,70 @@ impl ConfigValue {
     pub fn get_value<'a>(&'a self) -> &'a ConfigValueValue {
         &self.value
     }
+
+    fn from_toml(path: &Path, toml: toml::Value) -> CargoResult<ConfigValue> {
+        let value = match toml {
+            toml::String(val) => String(val),
+            toml::Array(val) => {
+                List(try!(result::collect(val.move_iter().map(|toml| {
+                    match toml {
+                        toml::String(val) => Ok(val),
+                        _ => Err(internal("")),
+                    }
+                }))))
+            }
+            toml::Table(val) => {
+                Table(try!(result::collect(val.move_iter().map(|(key, value)| {
+                    let value = raw_try!(ConfigValue::from_toml(path, value));
+                    Ok((key, value))
+                }))))
+            }
+            _ => return Err(internal(""))
+        };
+
+        Ok(ConfigValue { value: value, path: vec![path.clone()] })
+    }
+
+    fn merge(&mut self, from: ConfigValue) -> CargoResult<()> {
+        let ConfigValue { value, path } = from;
+        match (&mut self.value, value) {
+            (&String(ref mut old), String(ref mut new)) => {
+                mem::swap(old, new);
+                self.path = path;
+            }
+            (&List(ref mut old), List(ref mut new)) => {
+                old.extend(mem::replace(new, Vec::new()).move_iter());
+                self.path.extend(path.move_iter());
+            }
+            (&Table(ref mut old), Table(ref mut new)) => {
+                let new = mem::replace(new, HashMap::new());
+                for (key, value) in new.move_iter() {
+                    let mut err = Ok(());
+                    old.find_with_or_insert_with(key, value,
+                                                 |_, old, new| err = old.merge(new),
+                                                 |_, new| new);
+                    try!(err);
+                }
+                self.path.extend(path.move_iter());
+            }
+            (expected, found) => {
+                return Err(internal(format!("expected {}, but found {}",
+                                            expected.desc(), found.desc())))
+            }
+        }
+
+        Ok(())
+    }
+}
+
+impl ConfigValueValue {
+    fn desc(&self) -> &'static str {
+        match *self {
+            Table(..) => "table",
+            List(..) => "array",
+            String(..) => "string",
+        }
+    }
 }
 
 impl<E, S: Encoder<E>> Encodable<S, E> for ConfigValue {
@@ -137,13 +198,27 @@ pub fn get_config(pwd: Path, key: &str) -> CargoResult<ConfigValue> {
 }
 
 pub fn all_configs(pwd: Path) -> CargoResult<HashMap<String, ConfigValue>> {
-    let mut map = HashMap::new();
-
-    try!(walk_tree(&pwd, |file| extract_all_configs(file, &mut map)).map_err(|_|
-        human("Couldn't load Cargo configuration")));
+    let mut cfg = ConfigValue { value: Table(HashMap::new()), path: Vec::new() };
+
+    try!(walk_tree(&pwd, |mut file| {
+        let path = file.path().clone();
+        let contents = try!(file.read_to_string());
+        let file = path.filename_display().to_string();
+        let table = try!(cargo_toml::parse(contents.as_slice(),
+                                           file.as_slice()).chain_error(|| {
+            internal(format!("could not parse Toml manifest; path={}",
+                             path.display()))
+        }));
+        let value = try!(ConfigValue::from_toml(&path, toml::Table(table)));
+        try!(cfg.merge(value));
+        Ok(())
+    }).map_err(|_| human("Couldn't load Cargo configuration")));
 
 
-    Ok(map)
+    match cfg.value {
+        Table(map) => Ok(map),
+        _ => unreachable!(),
+    }
 }
 
 fn find_in_tree<T>(pwd: &Path,
@@ -192,80 +267,10 @@ fn walk_tree(pwd: &Path,
 
 fn extract_config(mut file: io::fs::File, key: &str) -> CargoResult<ConfigValue> {
     let contents = try!(file.read_to_string());
-    let toml = try!(cargo_toml::parse(contents.as_slice(),
-                                      file.path().filename_display()
-                                          .to_string().as_slice()));
-    let val = try!(toml.find_equiv(&key).require(|| internal("")));
-
-    let v = match *val {
-        toml::String(ref val) => String(val.clone()),
-        toml::Array(ref val) => {
-            List(val.iter().map(|s: &toml::Value| s.to_string()).collect())
-        }
-        _ => return Err(internal(""))
-    };
-
-    Ok(ConfigValue{ value: v, path: vec![file.path().clone()] })
-}
-
-fn extract_all_configs(mut file: io::fs::File,
-                       map: &mut HashMap<String, ConfigValue>) -> CargoResult<()> {
-    let path = file.path().clone();
-    let contents = try!(file.read_to_string());
-    let file = path.filename_display().to_string();
-    let table = try!(cargo_toml::parse(contents.as_slice(),
-                                       file.as_slice()).chain_error(|| {
-        internal(format!("could not parse Toml manifest; path={}",
-                         path.display()))
-    }));
-
-    for (key, value) in table.iter() {
-        match value {
-            &toml::String(ref val) => {
-                map.insert(key.clone(), ConfigValue {
-                    value: String(val.clone()),
-                    path: vec!(path.clone())
-                });
-            }
-            &toml::Array(ref val) => {
-                let config = map.find_or_insert_with(key.clone(), |_| {
-                    ConfigValue { path: vec!(), value: List(vec!()) }
-                });
-
-                try!(merge_array(config, val.as_slice(),
-                                       &path).chain_error(|| {
-                    internal(format!("The `{}` key in your config", key))
-                }));
-            },
-            _ => ()
-        }
-    }
+    let mut toml = try!(cargo_toml::parse(contents.as_slice(),
+                                          file.path().filename_display()
+                                              .to_string().as_slice()));
+    let val = try!(toml.pop_equiv(&key).require(|| internal("")));
 
-    Ok(())
-}
-
-fn merge_array(existing: &mut ConfigValue, val: &[toml::Value],
-               path: &Path) -> CargoResult<()> {
-    match existing.value {
-        String(_) => Err(internal("should be an Array, but it was a String")),
-        List(ref mut list) => {
-            let r: CargoResult<Vec<String>> = result::collect(val.iter().map(toml_string));
-            match r {
-                Err(_) => Err(internal("should be an Array of Strings, but \
-                                        was an Array of other values")),
-                Ok(new_list) => {
-                    list.push_all(new_list.as_slice());
-                    existing.path.push(path.clone());
-                    Ok(())
-                }
-            }
-        }
-    }
-}
-
-fn toml_string(val: &toml::Value) -> CargoResult<String> {
-    match val {
-        &toml::String(ref str) => Ok(str.clone()),
-        _ => Err(internal(""))
-    }
+    ConfigValue::from_toml(file.path(), val)
 }